/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import { cloneDeep } from '@baize/wdk';
import * as fse from 'fs-extra';
import path from 'path';

import { projectOhosConfigManager } from '../common/global/project-ohos-config-manager.js';
import { BuildArtifactConst, BuildArtifactExtension } from '../const/build-directory-const.js';
import { DefaultTargetConst } from '../const/common-const.js';
import { LegacyModuleModelImpl } from '../model/module/legacy-module-model-impl.js';
import { ModuleModelImpl } from '../model/module/module-model-impl.js';
import { ConfigJson } from '../options/configure/config-json-options.js';
import { ModuleJson } from '../options/configure/module-json-options.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';

import { ModuleTargetData } from './data/hap-task-target-data.js';
import { OhosHapTask } from './task/ohos-hap-task.js';
import ConfigOptObj = ConfigJson.ConfigOptObj;
import ModuleOptObj = ModuleJson.ModuleOptObj;
import { FileSet } from '@ohos/hvigor';
import { TaskInputValue } from '@ohos/hvigor';

import { DeviceTypeConst } from '../const/device-type-const.js';
import { CoreModuleModelImpl } from '../model/module/core-module-model-impl.js';
import { ModuleModel } from '../model/module/module-model.js';
import { AppOpt } from '../options/build/app-ohos-config.js';

import { ModuleTaskService } from './service/module-task-service.js';
import { INPUT_KEY } from './abstract-merge-profile.js';

/**
 * 生成hap级别的pack.info的抽象类
 *
 * @since 2022/5/21
 */
export abstract class AbstractMakePackInfo extends OhosHapTask {
  declareInputs(): Map<string, TaskInputValue> {
    const inputs = new Map<string, TaskInputValue>();
    const appOptFromConfig: AppOpt = cloneDeep(projectOhosConfigManager.getOverrides()?.appOpt);
    const bundleName = appOptFromConfig?.bundleName ?? this.targetData.getProduct().bundleName;
    if (bundleName) {
      inputs.set('bundleName', bundleName);
    }
    const artifactName = this.targetData.getTargetOpt().output?.artifactName;
    if (artifactName) {
      inputs.set('artifactName', artifactName);
    }
    const deviceTypes: string[] = this.targetData.getTargetDeviceType();
    inputs.set('deviceTypes', deviceTypes);
    inputs.set('compileSdkVersion', this.compileApiVersion);
    inputs.set('compatibleSdkVersion', this.compatibleApiVersion);
    const jsonProfileOpt = this.moduleModel.getJsonProfileOptByTargetName(this.targetName, this.isFaMode);
    // MergeProfile任务第二次增量跳过，第一次修改了缓存对象，导致第二次增量判断错误
    if (jsonProfileOpt.module.packageName !== undefined) {
      delete jsonProfileOpt.module.packageName;
    }
    if (jsonProfileOpt.module.installationFree !== undefined) {
      delete jsonProfileOpt.module.installationFree;
    }
    inputs.set(INPUT_KEY.moduleJsonOpt, JSON.stringify(jsonProfileOpt));
    if (!this.isFaMode) {
      return inputs;
    }
    const relatedEntryModules = this.service.getRelatedEntryModules();
    const moduleType = this.service.getModuleModel().getModuleType();
    if (ModuleTaskService.checkEntryModules(moduleType, relatedEntryModules)) {
      for (const entryName of relatedEntryModules!) {
        if (this.projectModel.getModuleModelByName(entryName)) {
          const entryTargetData = this.targetData.findTargetDataByName(entryName);
          const entryModuleModel = this.projectModel.getModuleModelByName(entryName)!;
          inputs.set(entryName, entryTargetData ? this.getEntryDeviceType(entryTargetData, entryModuleModel) : false);
        }
      }
    }
    return inputs;
  }

  declareOutputFiles(): FileSet {
    const outputFileSet = new FileSet();
    const relatedEntryModules = this.service.getRelatedEntryModules();
    const moduleType = this.service.getModuleModel().getModuleType();
    if (ModuleTaskService.checkEntryModules(moduleType, relatedEntryModules)) {
      for (const entryName of relatedEntryModules!) {
        if (this.projectModel.getModuleModelByName(entryName)) {
          const packInfoPath = path.resolve(this.pathInfo.getModuleBuildOutputPath(), entryName, BuildArtifactConst.PACK_INFO);
          outputFileSet.addEntry(packInfoPath);
        }
      }
    } else {
      const packInfoPath = path.resolve(this.pathInfo.getModuleBuildOutputPath(), BuildArtifactConst.PACK_INFO);
      outputFileSet.addEntry(packInfoPath);
    }
    return outputFileSet;
  }

  /**
   * MakePackInfo和LegacyMakePackInfo生成pack.info的共用流程
   *
   * @param log 日志
   * @param appObj pack.info中app标签对应的数据
   * @param apiVersion module对象中的apiVersion字段
   */
  protected doMakePackInfo(log: OhosLogger, appObj: object, apiVersion?: ConfigJson.ApiVersionObj): void {
    const selfDeviceTypes: string[] = this.targetData.getTargetDeviceType();
    const targetName = this.targetData.getTargetName();
    const optObj: ConfigOptObj | ModuleOptObj = this.moduleModel.getJsonObjByTargetName(targetName);
    const moduleObj: object = this.getModuleObj(selfDeviceTypes, this.moduleModel, apiVersion, targetName);
    const relatedEntryModules = this.service.getRelatedEntryModules();
    const defaultModuleObj =
      targetName === DefaultTargetConst.OHOS_TEST_TARGET
        ? this.getModuleObj(selfDeviceTypes, this.moduleModel, apiVersion, DefaultTargetConst.DEFAULT_TARGET)
        : undefined;
    const moduleType = this.service.getModuleModel().getModuleType();
    if (!ModuleTaskService.checkEntryModules(moduleType, relatedEntryModules)) {
      const modulesObj: object[] = defaultModuleObj ? [moduleObj, defaultModuleObj] : [moduleObj];
      this.buildPackInfo(log, '', optObj, appObj, modulesObj, undefined);
      return;
    }

    for (const entryName of relatedEntryModules!) {
      if (this.projectModel.getModuleModelByName(entryName)) {
        const entryTargetData = this.targetData.findRelatedTargetData(entryName);
        const entryModuleModel = this.projectModel.getModuleModelByName(entryName)! as LegacyModuleModelImpl | ModuleModelImpl;
        const entryDeviceTypes = this.getEntryDeviceType(entryTargetData, entryModuleModel);
        const entryModuleObj: object = this.getModuleObj(
          this.getDeviceTypeSubset(log, entryName, entryDeviceTypes, selfDeviceTypes),
          entryModuleModel,
          apiVersion,
          targetName,
        );
        const modulesObj: object[] = [moduleObj, entryModuleObj];
        if (defaultModuleObj) {
          const defaultEntryModuleObj: object = this.getModuleObj(
            this.getDeviceTypeSubset(log, entryName, entryDeviceTypes, selfDeviceTypes),
            entryModuleModel,
            apiVersion,
            DefaultTargetConst.DEFAULT_TARGET,
          );
          modulesObj.push(defaultModuleObj, defaultEntryModuleObj);
        }
        this.buildPackInfo(log, entryName, optObj, appObj, modulesObj, entryModuleModel.getJsonObjByTargetName(targetName));
      }
    }
  }

  /**
   * 构造packinfo对象
   *
   * @param log 日志
   * @param entryName 关联的entry模块名
   * @param configObj 模块下module.json5或config.json中的信息
   * @param appObj 工程级别描述信息
   * @param modulesObj 模块级别描述信息
   * @param entryConfigObj 关联的entry模块下module.json5或config.json中的信息
   */
  protected buildPackInfo(
    log: OhosLogger,
    entryName: string,
    configObj: ConfigOptObj | ModuleOptObj,
    appObj: object,
    modulesObj: object[],
    entryConfigObj: ConfigOptObj | ModuleOptObj | undefined,
  ) {
    const fullHapName = this.targetData.getModuleTargetOutputFileName(entryName, null);
    const noSuffixHapName =
      this.targetData.getTargetOpt().output?.artifactName ?? fullHapName.substring(0, fullHapName.lastIndexOf(BuildArtifactExtension.DOT_HAP));
    const fullLiteHapName = this.targetData.getModuleTargetOutputFileName(entryName, null, undefined, DeviceTypeConst.LITE);
    const noSuffixLiteHapName = fullLiteHapName.substring(0, fullLiteHapName.lastIndexOf(BuildArtifactExtension.DOT_HAP));

    const packageObjs: object[] = entryConfigObj
      ? this.getPackageObj(this.targetData.findTargetDataByName(entryName) ?? this.targetData, configObj, noSuffixHapName, noSuffixLiteHapName)
      : this.getPackageObj(this.targetData.findTargetDataByName(entryName) ?? this.targetData, configObj, noSuffixHapName, noSuffixLiteHapName);
    const packInfo: object = this.getPackInfoObj(appObj, modulesObj, packageObjs);
    this.outputJSON(log, packInfo, this.targetData.getPathInfo().getModuleBuildOutputPath(), entryName);
  }

  protected processForms(formsObj: ConfigJson.FormsObj[] | ModuleJson.FormObj[] | undefined): object[] | undefined {
    if (formsObj === undefined) {
      return undefined;
    }
    const processedForms: object[] = [];
    for (let i = 0; i < formsObj.length; i++) {
      const orgFormObj: ConfigJson.FormsObj | ModuleJson.FormObj = formsObj[i];
      const formObj: object = {
        name: orgFormObj.name,
        type: 'JS',
        updateEnabled: orgFormObj.updateEnabled,
        scheduledUpdateTime: orgFormObj.scheduledUpdateTime,
        updateDuration: orgFormObj.updateDuration,
        supportDimensions: orgFormObj.supportDimensions,
        defaultDimension: orgFormObj.defaultDimension,
      };
      processedForms.push(formObj);
    }
    return processedForms;
  }

  protected processAbilities(abilitiesObj: ConfigJson.AbilitiesObj[] | ModuleJson.AbilityObj[] | undefined): object[] | undefined {
    if (abilitiesObj === undefined) {
      return undefined;
    }
    const processedAbilities: object[] = [];
    for (const { forms, label, name, visible } of abilitiesObj) {
      const abilityObj: object = {
        name: name,
        label: label,
        visible: visible,
        forms: this.processForms(forms as ConfigJson.FormsObj[] | ModuleJson.FormObj[] | undefined),
      };
      processedAbilities.push(abilityObj);
    }
    return processedAbilities;
  }

  /**
   * 得到packinfo的packges标签需要的数据
   *
   * @param targetData 模块的target数据
   * @param moduleOptObj 模块下module.json5或config.json中的信息
   * @param noSuffixHapName 生成的无后缀hap包名
   * @param noSuffixLiteHapName 生成的无后缀lite hap包名
   */
  protected abstract getPackageObj(
    targetData: ModuleTargetData,
    moduleOptObj: ConfigOptObj | ModuleOptObj,
    noSuffixHapName: string,
    noSuffixLiteHapName?: string,
  ): object[];

  /**
   * 获取modules标签下单个module对象数据
   *
   * @param deviceTypes 设备类型
   * @param moduleModel 模块数据模型
   * @param apiVersion apiVersion标签对应数据
   * @param targetName target名
   */
  protected abstract getModuleObj(
    deviceTypes: string[],
    moduleModel: CoreModuleModelImpl,
    apiVersion: ConfigJson.ApiVersionObj | undefined,
    targetName: string,
  ): object;

  private outputJSON(log: OhosLogger, packInfo: object, moduleBuildOutputPath: string, entryName: string): void {
    log.anonymizeDebug('Module Pack Info: ', packInfo);
    fse.outputJSONSync(path.resolve(moduleBuildOutputPath, entryName, BuildArtifactConst.PACK_INFO), packInfo);
  }

  private getPackInfoObj(appObj: object, modulesObj: object[], packageObjs: object[]): object {
    return {
      summary: {
        app: appObj,
        modules: modulesObj,
      },
      packages: packageObjs, // 因为是hap级别的pack.info,所以数组只有一个对象
    };
  }

  /**
   * 检查FA模型feature模块所关联的entry模块是否存在对应的target，如果不存在，给出报错提示
   *
   * @param log 日志
   * @param targetName feature模块的target名
   * @param entryName entry模块名
   * @param entryTarget entry模块对应的target数据
   */
  private checkEntryTarget(log: OhosLogger, targetName: string, entryName: string, entryTarget: ModuleTargetData | undefined): void {
    if (!entryTarget && this.isFaMode) {
      log.printErrorExit(
        'FA_NO_ENTRY_TARGET',
        [targetName, entryName],
        [[this.moduleName, entryName]]
      );
    }
  }

  /**
   * 返回entry模块的deviceType，如果entry对应的target不存在，则返回module.json5中的deviceType
   * @param entryTargetData entry模块对应的target数据
   * @param entryModuleModel entry模块模型
   * @returns string[] deviceType列表
   */
  private getEntryDeviceType(entryTargetData: ModuleTargetData | undefined, entryModuleModel: ModuleModel): string[] {
    return entryTargetData ? entryTargetData!.getTargetDeviceType() : entryModuleModel.getDeviceTypes();
  }

  /**
   * 去除entry模块的deviceType中不存在于feature模块中deviceType的元素，保证其是feature模块中deviceType的子集
   *
   * @param log 日志
   * @param entryName entry模块名
   * @param entryDeviceTypes entry模块的deviceType
   * @param featureDeviceTypes feature模块的deviceType
   * @returns string[] 修剪后deviceType列表
   */
  private getDeviceTypeSubset(log: OhosLogger, entryName: string, entryDeviceTypes: string[], featureDeviceTypes: string[]): string[] {
    const set: Set<string> = new Set<string>(featureDeviceTypes);
    const res: string[] = entryDeviceTypes.filter((deviceType) => {
      return set.has(deviceType);
    });
    if (res.length === 0) {
      log.warn(`The device types of ${this.moduleModel.getName()} and ${entryName} are different. ` + `Invalid HAP ${this.moduleModel.getName()}.`);
    }
    return res;
  }
}
